This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / routes / [handle] / [repo] / +layout.ts
4.2 kB 115 lines
1import { error, redirect } from "@sveltejs/kit"; 2import { createBobbinClient } from "$lib/api/client"; 3import { count } from "$lib/api/count"; 4import { getStarRkey } from "$lib/api/graph"; 5import { IdentityCache, resolveMiniDoc } from "$lib/api/identity"; 6import { getDefaultBranch } from "$lib/api/knot"; 7import { parallel, toHttpError } from "$lib/api/load"; 8import { getRepo } from "$lib/api/records"; 9import { repoNameOf, resolveRepoByName } from "$lib/api/repo"; 10import { didFromUri, rkeyFromUri } from "$lib/api/uri"; 11import type { BobbinContext } from "$lib/api/client"; 12import type { RepoCounts, RepoInfo, RepoSource } from "$lib/components/repo/types"; 13import type { LayoutLoad } from "./$types"; 14 15const FALLBACK_BRANCH = "main"; 16 17const resolveSource = async ( 18 ctx: BobbinContext, 19 uri: string | undefined 20): Promise<RepoSource | null> => { 21 if (!uri?.startsWith("at://")) return null; 22 try { 23 const view = await getRepo(ctx, uri); 24 const ownerDid = didFromUri(view.uri); 25 const owner = await new IdentityCache(ctx).resolve(ownerDid).catch(() => null); 26 return { ownerHandle: owner?.handle ?? ownerDid, name: repoNameOf(view) }; 27 } catch { 28 // a fork whose source is gone still renders, just without the attribution 29 return null; 30 } 31}; 32 33// the layout reset also cuts us off from `[handle]/+layout.ts`, so identity gets 34// resolved again here 35export const load: LayoutLoad = async (event) => { 36 const parent = await event.parent(); 37 const identifier = decodeURIComponent(event.params.handle); 38 const name = decodeURIComponent(event.params.repo); 39 40 // rejects bare words so unrelated paths 404 instead of resolving as actors 41 if (!identifier.startsWith("did:") && !identifier.includes(".")) { 42 error(404, "Not found"); 43 } 44 45 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); 46 const doc = await resolveMiniDoc(ctx, identifier).catch((cause) => 47 toHttpError(cause, "Could not resolve user") 48 ); 49 50 // redirects dids and stale handles to the canonical handle url 51 const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null; 52 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) { 53 redirect(307, `/${canonical}/${event.params.repo}${event.url.search}`); 54 } 55 56 const view = await resolveRepoByName(ctx, doc.did, name).catch((cause) => 57 toHttpError(cause, "Could not load repository") 58 ); 59 if (!view) error(404, `${doc.handle}/${name} does not exist`); 60 61 const record = view.value; 62 const repoDid = record.repoDid; 63 const viewerDid = parent.auth?.did; 64 65 const stats = await parallel({ 66 // only the knot knows the default branch. a knot that is down or still 67 // syncing shouldn't take out the whole layout 68 defaultBranch: getDefaultBranch(ctx, { repo: view.uri }) 69 .then((branch) => branch.name) 70 .catch(() => null), 71 stars: repoDid 72 ? count(ctx, "sh.tangled.feed.countStars", repoDid).catch(() => null) 73 : Promise.resolve(null), 74 // the tabs count what is still open, closed ones matter on their own pages 75 issues: repoDid 76 ? count(ctx, "sh.tangled.repo.countIssues", repoDid, { state: "open" }).catch(() => null) 77 : Promise.resolve(null), 78 pulls: repoDid 79 ? count(ctx, "sh.tangled.repo.countPulls", repoDid, { status: "open" }).catch(() => null) 80 : Promise.resolve(null), 81 forks: repoDid 82 ? count(ctx, "sh.tangled.repo.countForks", repoDid).catch(() => null) 83 : Promise.resolve(null), 84 viewerStarRkey: 85 viewerDid && repoDid 86 ? getStarRkey(ctx, viewerDid, repoDid).catch(() => null) 87 : Promise.resolve(null), 88 source: resolveSource(ctx, record.source) 89 }); 90 91 const repo: RepoInfo = { 92 uri: view.uri, 93 rkey: rkeyFromUri(view.uri), 94 name: repoNameOf(view), 95 ownerDid: doc.did, 96 ownerHandle: doc.handle, 97 repoDid, 98 knot: record.knot, 99 spindle: record.spindle, 100 description: record.description, 101 website: record.website, 102 topics: record.topics, 103 source: stats.source ?? undefined, 104 defaultBranch: stats.defaultBranch ?? FALLBACK_BRANCH 105 }; 106 107 const counts: RepoCounts = { 108 stars: stats.stars?.count ?? 0, 109 issues: stats.issues?.count ?? 0, 110 pulls: stats.pulls?.count ?? 0, 111 forks: stats.forks?.count ?? 0 112 }; 113 114 return { repo, counts, viewerStarRkey: stats.viewerStarRkey }; 115};